package Element;

import java.awt.*;
import java.awt.geom.*;

import Element.Geom.*;
import DistSys.*;

public class Line extends AbsElem implements Cloneable {
    public static AbsElem objActiveTemp;   //a developer's version of ActiveTemplate
    public static AbsElem objActiveTemp2;  //a developer's version of ActiveTemplate

    public Line(){ }

    public Object clone(){
        Line created = new Line();
        this.copyTo(created); // copy AbsElem attributes to created
        //created.setDftTemp(this.getDftTempName());
        //created.rel  = (ReliaInd) (this.rel.clone());
        //created.ckt  = (CktLaw) (this.ckt.clone());
        created.geom = (LineGeom) (((LineGeom)this.geom).clone());
        return created;
    }

    public int getType() {
        if (((LineGeom)getElemGeomObj()).getIsCable())
            return (isTemplate() ? SysConst.TEMPLTYPE_CABLE :
                SysConst.TYPE_CABLE);
        else
            return (isTemplate() ? SysConst.TEMPLTYPE_LINE :
                SysConst.TYPE_LINE);
    }

    public void setLengthToAutoValue(){
        AbsElemGeom g = geom;
        double len = (g.x1Stored - g.x2Stored)*(g.x1Stored - g.x2Stored);
        len = len + (g.y1Stored - g.y2Stored)*(g.y1Stored - g.y2Stored);
        len = Math.sqrt( len );
        setData(AbsElem.DATA_LENGTH, len * Sys.prjInfo.lengthPerUnit);
    }

    public void draw(Graphics g) {
        int x1 = geom.x1, y1 = geom.y1, x2 = geom.x2, y2 = geom.y2;

        if (((LineGeom)geom).getIsCable()) { // need to draw in dashed line
            Graphics2D g2 = (Graphics2D) g;
            BasicStroke dftBS = (BasicStroke) g2.getStroke();
            float[] dash = {1.5f, 3.0f};
            BasicStroke newBS = new BasicStroke(dftBS.getLineWidth(),
                                                dftBS.getEndCap(),
                                                dftBS.getLineJoin(),
                                                dftBS.getMiterLimit(),
                                                dash,  // customized dash
                                                dftBS.getDashPhase());
            g2.setStroke(newBS);       // update stroke
            //g2.drawLine(x1,y1, x2,y2);
            drawLineForLargeCoord( g,x1,y1, x2,y2);
            g2.setStroke(dftBS);       // set stroke back to default one
        }
        //else g.drawLine(x1,y1, x2,y2);
        drawLineForLargeCoord( g,x1,y1, x2,y2);
    }

    /**
     * this is added due to a bug in Java Plug-in. It has not been fixed by Sun
     * (in progress).
     * Bug ID = 4265778.
     * Description: Java2D incorrectly renders objects with large coordinates
     */
    private void drawLineForLargeCoord( Graphics g, int x1, int y1, int x2, int y2){
        int threshold= 2000;
        Point tmp=null, pt1=null, pt2=null;

        Line2D.Double boundaryLine[] = new Line2D.Double[4];
        Line2D.Double originialLine= new Line2D.Double( x1, y1, x2, y2);
        Rectangle rect = new Rectangle( 0,0, threshold + 2,threshold +2 );

        if( rect.contains(x1, y1) && rect.contains(x2,y2) ){
            g.drawLine (x1, y1, x2, y2);
            return;
        }

        boundaryLine[0] = new Line2D.Double( 0, 0, 0, threshold);
        boundaryLine[1] = new Line2D.Double( 0, threshold, threshold, threshold);
        boundaryLine[2] = new Line2D.Double( threshold, threshold, threshold, 0);
        boundaryLine[3] = new Line2D.Double( threshold, 0, 0, 0);
        for (int i=0; i<4; i++) {
            tmp = getLineIntersection( originialLine, boundaryLine[i]);
            if( tmp != null) {
                if(pt1 == null){ pt1 = tmp; }
                else {
                    if( tmp.x != pt1.x || tmp.y != pt1.y ){
                        pt2 = tmp;
                        break;
                    } else {
                        //nothing
                    }
                }//end of else , i.e. pt1 != null
            }
        }//end of for

        if( pt1 == null ){ //pt2 must be null
            g.drawLine (x1, y1, x2, y2);
            return;
        } else{
            if( pt2 !=null){
                g.drawLine(pt1.x, pt1.y, pt2.x, pt2.y);
                return;
            } else{
                if( rect.contains(x1, y1) ) {
                    g.drawLine (x1, y1, pt1.x, pt1.y);
                    return;
                } else { //contains x2, y2
                    g.drawLine (x2, y2, pt1.x, pt1.y);
                    return;
                }

            }
        }

    }

    private Point getLineIntersection(Line2D l1, Line2D l2) {
        if (!l1.intersectsLine(l2)) return null;
        double px = l1.getX1(),
        py = l1.getY1(),
        rx = l1.getX2()-px,
        ry = l1.getY2()-py;
        double qx = l2.getX1(),
        qy = l2.getY1(),
        sx = l2.getX2()-qx,
        sy = l2.getY2()-qy;

        double det = sx*ry - sy*rx;
            if (det == 0) {
            return null;
        } else {
            double z = (sx*(qy-py)+sy*(px-qx))/det;
            return new Point((int)(px+z*rx), (int)(py+z*ry));
        }
    } // end intersection line-line


    // Dummy implementation of the abstract method since we inherit draw(g)
    public void draw(Graphics2D g2, double dist, double theta) { }

    /**
     * Returns whether the data field with the given index "makes sense" for
     * this element type or not.
     *
     * @param index  a <code>int</code> specifying the index.
     *
     * @return  <code>true</code> if the data field "makes sense",
     *          <code>false</code> otherwise
     */
    public boolean getDataExists(int index) {
        // A switch is used here to be independent of the
        // AbsElem.DATA_* constant's real values.
        switch(index) {
            case AbsElem.DATA_LENGTH:
            case AbsElem.DATA_ZONE:
            case AbsElem.DATA_LAYER:
            case AbsElem.DATA_RATED_POWER:
            case AbsElem.DATA_RATED_CURR:
            case AbsElem.DATA_ZONE_BUFFER0:
            case AbsElem.DATA_ZONE_BUFFER2:
            case AbsElem.DATA_ZONE_BUFFER4:
            case AbsElem.DATA_ZONE_BUFFER1:
            case AbsElem.DATA_ZONE_BUFFER3:
            case AbsElem.DATA_TEMP_FAULT:
            case AbsElem.DATA_SUST_FAULT:
            case AbsElem.DATA_OPEN_CKT:
            case AbsElem.DATA_MTTR:
            case AbsElem.DATA_VOLT_SAG_LEVEL:
            case AbsElem.DATA_RESISTANCE:
            case AbsElem.DATA_REACTANCE:
            case AbsElem.DATA_SHUNT_COND:
            case AbsElem.DATA_SHUNT_SUCEP:
            case AbsElem.DATA_CAPITAL_COST:
            case AbsElem.DATA_OM_COST:
            case AbsElem.DATA_FONT_SIZE:
            case AbsElem.DATA_IN_OPT_LIB:
            case AbsElem.DATA_MAINT_DUR:
            case AbsElem.DATA_MAINT_RATE:
            case AbsElem.DATA_OPTIMIZABLE:
            case AbsElem.DATA_PHASE:
            case AbsElem.DATA_STUDY_AREA:
            case AbsElem.DATA_UPGRADE_COST:
            case AbsElem.DATA_YEAR:
                return true;
            default:
                return false;
        }
    }

    /**
     * Returns whether the result field with the given index "makes sense" for
     * this element type or not.
     *
     * @param index  a <code>int</code> specifying the index.
     *
     * @return  <code>true</code> if the result field "makes sense",
     *          <code>false</code> otherwise
     */
    public boolean getResultExists(int index) {
        // A switch is used here to be independent of the
        // AbsElem.RES_* constant's real values.
        switch(index) {
            case AbsElem.RES_QUERY_SCORE:
            case AbsElem.RES_QUERY_SCORE_PERC:
            case AbsElem.RES_MOMENT_INT:
            case AbsElem.RES_SUST_INT:
            case AbsElem.RES_OUT_DUR:
            case AbsElem.RES_VOLT_SAGS:
            case AbsElem.RES_CAPA_CONS:
            case AbsElem.RES_CAPA_CONS_KVA:
            case AbsElem.RES_KVA_FLOW:
            case AbsElem.RES_PF:
            case AbsElem.RES_VOLT_MAG:
            case AbsElem.RES_VOLT_ANG:
            case AbsElem.RES_CURR_MAG:
            case AbsElem.RES_CURR_ANG:
            case AbsElem.RES_SHORTCIRC_CURR:
            case AbsElem.RES_VOLT_DROP:
            case AbsElem.RES_CURR_LOADING:
            case AbsElem.RES_KVA_RATING:
            case AbsElem.RES_MAIFI:
            case AbsElem.RES_SAG:
            case AbsElem.RES_SAIDI:
            case AbsElem.RES_SAIFI:
            case AbsElem.RES_VOLT_MAG_NODE1:
            case AbsElem.RES_VOLT_ANG_NODE1:
            case AbsElem.RES_VOLT_SAGS_ON_TGT:
            case AbsElem.RES_VOLT_SAGS_NODE1:
            case AbsElem.RES_PROTECT_ZONE:
            case AbsElem.RES_SA_MOMENT_INT:
            case AbsElem.RES_SA_SUST_INT:
            case AbsElem.RES_SA_DUR_SI:
            case AbsElem.RES_OLV_MI:
            case AbsElem.RES_OLV_SI:
            case AbsElem.RES_OLV_DUR_SI:
            case AbsElem.RES_UVV_MI:
            case AbsElem.RES_UVV_SI:
            case AbsElem.RES_UVV_DUR_SI:
                return true;
            default:
                return false;
        }
    }
}